package com.jonlandrum.collections.BinarySearchTree;

public class LinkedBinarySearchNode<T extends Comparable<T>> implements BinarySearchNode<T> {
    private T data = null;
    private BinarySearchNode<T> parent = null;
    private BinarySearchNode<T> left = null;
    private BinarySearchNode<T> right = null;

    /**
     * Constructs an empty LinkedBinarySearchNode.
     */
    public LinkedBinarySearchNode() {}

    /**
     * Constructs a new LinkedBinarySearchNode with the given data.
     *
     * @param data The data to store in this node
     */
    public LinkedBinarySearchNode(T data) {
        this.data = data;
    }

    /**
     * Returns the data stored in this node.
     *
     * @return The data stored in this node
     */
    @Override
    public T getData() {
        return this.data;
    }

    /**
     * Sets the data element of this node to null.
     */
    @Override
    public void setData() {
        this.data = null;
    }

    /**
     * Sets the data element of this node to the data parameter.
     *
     * @param data The data to store in this node
     */
    @Override
    public void setData(T data) {
        this.data = data;
    }

    /**
     * Returns the parent of this node.
     *
     * @return The parent of this node
     */
    @Override
    public BinarySearchNode<T> getParent() {
        return this.parent;
    }

    /**
     * Sets the parent of this node to null.
     */
    @Override
    public void setParent() {
        this.parent = null;
    }

    /**
     * Sets the parent of this node to the node parameter.
     *
     * @param node The node to set as this node's parent
     */
    @Override
    public void setParent(BinarySearchNode<T> node) {
        this.parent = node;
    }

    /**
     * Returns the left child of this node.
     *
     * @return The left child of this node
     */
    @Override
    public BinarySearchNode<T> getLeft() {
        return this.left;
    }

    /**
     * Sets the left child of this node to null.
     */
    @Override
    public void setLeft() {
        this.left = null;
    }

    /**
     * Sets the left child of this node to the node parameter.
     *
     * @param node The node to set as this node's left child
     */
    @Override
    public void setLeft(BinarySearchNode<T> node) {
        this.left = node;
    }

    /**
     * Returns the right child of this node.
     *
     * @return The right child of this node
     */
    @Override
    public BinarySearchNode<T> getRight() {
        return this.right;
    }

    /**
     * Sets the right child of this node to null.
     */
    @Override
    public void setRight() {
        this.right = null;
    }

    /**
     * Sets the right child of this node to the node parameter.
     *
     * @param node The node to set as this node's right child
     */
    @Override
    public void setRight(BinarySearchNode<T> node) {
        this.right = node;
    }

    /**
     * Returns the node that replaces this node.
     *
     * <p>This method first attempts to locate the in-order successor of this node.
     * The in-order successor of a given node is the next element in the collection in sorted order.
     * If an in-order successor doesn't exist, this method returns the in-order predecessor.
     * If neither exists, this method returns an empty node.</p>
     *
     * @return The node that replaces this node
     */
    @Override
    public BinarySearchNode<T> getReplacement() {
        LinkedBinarySearchNode<T> r = this;
        if (r.hasRight()) {
            r = (LinkedBinarySearchNode<T>) r.getRight();
            while (r.hasLeft()) {
                r = (LinkedBinarySearchNode<T>) r.getLeft();
            }
        } else if (r.hasLeft()) {
            r = (LinkedBinarySearchNode<T>) r.getLeft();
            while (r.hasRight()) {
                r = (LinkedBinarySearchNode<T>) r.getRight();
            }
        } else {
            r = new LinkedBinarySearchNode<>();
        }
        return r;
    }

    /**
     * Returns true if the data element of this node is not null.
     *
     * @return True if the data element of this node is not null
     */
    @Override
    public boolean hasData() {
        return this.data != null;
    }

    /**
     * Returns true if the parent of this node is not null.
     *
     * @return True if the parent of this node is not null
     */
    @Override
    public boolean hasParent() {
        return this.parent != null;
    }

    /**
     * Returns true if the left child of this node is not null.
     *
     * @return True if the left child of this node is not null
     */
    @Override
    public boolean hasLeft() {
        return this.left != null;
    }

    /**
     * Returns true if the right child of this node is not null.
     *
     * @return True if the right child of this node is not null
     */
    @Override
    public boolean hasRight() {
        return this.right != null;
    }

    /**
     * Returns true if either the left or right child of this node is not null.
     *
     * @return True if either the left or right child of this node is not null
     */
    @Override
    public boolean hasChild() {
        return this.hasLeft() || this.hasRight();
    }

    /**
     * Returns true if this node is the left child of its parent.
     *
     * @return True if this node is the left child of its parent
     */
    @Override
    public boolean isLeft() {
        return this.hasParent() &&
                this.getParent().hasLeft() &&
                this.getParent().getLeft() == this;
    }

    /**
     * Returns true if this node is the right child of its parent.
     *
     * @return True if this node is the right child of its parent
     */
    @Override
    public boolean isRight() {
        return this.hasParent() &&
                this.getParent().hasRight() &&
                this.getParent().getRight() == this;
    }

    /**
     * Returns a String containing the data element of this node.
     *
     * @return A String containing the data element of this node
     */
    @Override
    public String toString() {
        return this.data == null ? "" : this.data.toString();
    }

    /**
     * Returns an integer representing the comparison between this node and that node.
     * <p>
     * <p>A negative value means this node is less than that node.
     * A positive value means this node is greater than that node.
     * A zero value means this node and that node are equal.</p>
     *
     * @param that A node against which this node should be compared
     * @return An integer representing the comparison between this node and that node
     */
    @Override
    public int compareTo(BinarySearchNode<T> that) {
        return this.getData().compareTo(that.getData());
    }
}